package gogpc

//#cgo CFLAGS: -O3
//#cgo LDFLAGS: -lm
//#include "gogpc.h"
import "C"

const EPS = float64(C.GPC_EPSILON)

type Point []float64
type Bound struct {
	is_hole bool
	point   []Point
}
type Poly struct {
	bound []Bound
	ws    []float64
	np	[]int
	ib	int
}

type Drawer interface {
	Draw (p1, p2 []float64, class int)
}

func sum_int(a []int) int {
	res := 0
	for _, i := range a {
		res += i
	}
	return res
}

func InitPoly() *Poly {
	var p Poly
	p.ws = make([]float64, 0)
	p.bound = make([]Bound, 0)
	p.np = make([]int, 0)
	p.ib = -1
	return &p
}

func (p *Poly) NewBound(is_hole bool) {
	b := Bound{is_hole: is_hole, point: make([]Point, 0)}
	p.bound = append(p.bound, b)
	p.np = append(p.np, 0)
	p.ib ++ 
}

func (p *Poly) NewPoint(x, y float64) {
	p.ws = append(p.ws, x, y, 0)
	p.np[p.ib] ++
}

func (p *Poly) Finish () {
	tip := 0
	for i := 0; i < len(p.bound); i ++ {
		p.bound[i].point = make ([]Point, p.np[i])
		for j := 0; j < p.np[i]; j ++ {
			p.bound[i].point[j] = p.ws [tip * 3 : (tip + 1) * 3]
			tip ++ 
		}
	}
}

func (p *Poly) GetNPoint() []int {
	return p.np
}

func (p *Poly) GetTotalNPoint() int {
	return sum_int(p.GetNPoint())
}

func (p *Poly) IsEmpty() bool {
	return len(p.bound) == 0
}

func (p *Poly) SetPoly(id int) {
	C.SetNewPoly(C.int(id))
	for _, b := range p.bound {
		C.SetNewBound(C.int(len(b.point)))
		for _, point := range b.point {
			C.SetNewPoint(C.double(point[0]), C.double(point[1]))
		}
		C.SetEndBound()
	}
	C.SetEndPoly()
}

func GetNPoint(id int) []int {
	cid := C.int(id)
	nb := int(C.GetNBound(cid))
	np := make([]int, nb)
	for i := 0; i < nb; i++ {
		np[i] = int(C.GetNPoint(cid, C.int(i)))
	}
	return np
}

func GetTotalNPoint (id int) int {
	return sum_int (GetNPoint(id))
}

func GetPoly(id int) *Poly {
	p := InitPoly()

	cid := C.int(id)
	nb := int(C.GetNBound(cid))
	for i := 0; i < nb; i++ {
		cib := C.int(i)
		var is_hole bool
		if byte(C.GetIsHole(cid, cib)) != 0 { is_hole = true }
		p.NewBound(is_hole)
		np := int(C.GetNPoint(cid, cib))
		for j := 0; j < np; j++ {
			civ := C.int(j)
			p.NewPoint(float64(C.GetPointX(cid, cib, civ)), float64(C.GetPointY(cid, cib, civ)))
		}
	}

	p.Finish()
	return p
}

func PolyDiff(p1, p2, res int) {
	C.PolyDiff(C.int(p1), C.int(p2), C.int(res))
}

func PolyUnion(p1, p2, res int) {
	C.PolyUnion(C.int(p1), C.int(p2), C.int(res))
}

func PolyInter(p1, p2, res int) {
	C.PolyInter(C.int(p1), C.int(p2), C.int(res))
}

func (p Poly) Draw(d Drawer, class int) {
	for _, b := range p.bound {
		np := len(b.point)
		for i := 0; i < np; i++ {
			ii := (i + 1) % np
			d.Draw (b.point[i], b.point[ii], class)
		}
	}
}

func (p *Poly) CalBoundingBox() (pmin, pmax []float64) {
	pmin = make(Point, 2)
	pmax = make(Point, 2)

	is_first := true
	for _, bound := range p.bound {
		if bound.is_hole {
			continue
		}
		for _, point := range bound.point {
			if is_first {
				for i := 0; i < 2; i++ {
					pmin[i] = point[i]
					pmax[i] = point[i]
				}
				is_first = false
			} else {
				for i := 0; i < 2; i++ {
					if point[i] < pmin[i] {
						pmin[i] = point[i]
					}
					if point[i] > pmax[i] {
						pmax[i] = point[i]
					}
				}
			}
		}
	}

	return
}
